// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef FUNCTION_HH
# define FUNCTION_HH

# include "signal.hh"

namespace funky {
  template <typename F>
  struct function_signal;

  template <typename Sample, typename Duration>
  struct function_signal<Sample(Duration)> {
  private:
    typedef std::function<Sample(const Duration&)> fun_type;
    Duration period;
    Duration position;
    fun_type f;

  public:
    function_signal(fun_type&& f, Duration period)
      : period(period), position(), f(std::move(f)) {}

    function_signal(const fun_type& f, Duration period)
      : period(period), position(), f(f) {}

    function_signal(const function_signal& other)
      : period(other.period),
	position(other.position),
	f(other.f) {
    }

    function_signal(function_signal&& other):
      period(std::move(other.period)),
      position(std::move(other.position)),
      f(std::move(other.f)) {
    }

    function_signal& operator=(const function_signal& other) {
      return *this = function_signal(other);
    }

    function_signal& operator=(function_signal&& other) {
      std::swap(position, other.position);
      std::swap(period, other.period);
      std::swap(f, other.f);
      return *this;
    }

    function_signal& operator+=(const Duration& d) {
      position += d;
      if (position > period)
	position -= period;
      return *this;
    }

    function_signal& operator++() {
      position = (position + 1) % period;
      return *this;
    }

    function_signal operator++(int) const {
      function_signal ret{*this};
      ++*this;
      return std::move(ret);
    }

    Sample operator*() const {
      return f(position);
    }
  };

  template <typename Sample, typename Duration>
  struct signal_traits<function_signal<Sample(Duration)> > {
    typedef Duration duration;
    typedef Sample sample;
  };
}

#endif
